﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using PS.Algorithms;
using PS.DataTypes;
using PS.Engine.DataTypes;
using PS.Engine.GameComponents;
using PS.Engine.Helper;
using PS.Stereotypes;
using System.Diagnostics;
using PS.Engine.Strategies;

namespace PS.Engine
{
    internal class World
    {
        #region Constantes

        private const float PedestrianDistanceOffset = 20f;

        #endregion

        #region Constructors

        internal World()
        {
            Obstacles = new List<Obstacle>();
            Pedestrians = new List<Pedestrian>();
            Waypoints = new WaypointList();
        }

        #endregion

        #region Properties

        internal List<Obstacle> Obstacles { get; private set; }
        internal List<Pedestrian> Pedestrians { get; private set; }
        internal WaypointList Waypoints { get; private set; }
        internal Vertex[] Vertices { get; set; }
        internal Edge[] Edges { get; set; }

        #endregion

        #region internal Methods

        internal void LoadEnvironment(string serializationDocumentPath, Game game)
        {
            // load environment from xml
            string loadedXml = SerializationHelper.LoadXml(serializationDocumentPath);
            PS.Engine.DataTypes.Environment environment = (PS.Engine.DataTypes.Environment)SerializationHelper.DeserializeXml(loadedXml, typeof(PS.Engine.DataTypes.Environment));

            LoadObstacles(environment, game);

            LoadPedestrianZones(environment, game);

            LoadPedestrians(environment, game);

            LoadVertices();

            LoadEdgesFromWaypoints();
        }

        internal bool PedestrianCollisionHappens(Pedestrian pedestrian, out Pedestrian collisionar)
        {
            bool collision = false;
            collisionar = null;

            foreach (Pedestrian p in Pedestrians)
            {
                if (p == pedestrian) continue;

                float distance = Vector2.Distance(p.Location, pedestrian.Location);
                collision = distance < PedestrianDistanceOffset;
                collisionar = p;
                if (collision) break;
            }

            return collision;
        }

        #endregion

        #region private Methods

        private void LoadObstacles(PS.Engine.DataTypes.Environment environment, Game game)
        {
            foreach (ObstacleSerializable obstacleSerialized in environment.Obstacles)
            {
                // create obstacle
                Obstacle obstacle = new Obstacle(game);

                // add vertices
                obstacle.Vertices = obstacleSerialized.Vertices;

                foreach (Vector2 waypointVector in obstacle.Waypoints)
                {
                    EnsureWaypoint(waypointVector, game);
                }

                // welcome to the real world obstacle
                Obstacles.Add(obstacle);
            }
        }

        private void LoadPedestrianZones(PS.Engine.DataTypes.Environment environment, Game game)
        {
            foreach (PedestrianZone zone in environment.Zones)
            {
                List<PedestrianSerializable> pedestrians = zone.GetPedestrians();
                foreach (PedestrianSerializable pedestrian in pedestrians)
                {
                    AddPedestrian(pedestrian, game);
                }
            }
        }

        private void LoadPedestrians(PS.Engine.DataTypes.Environment environment, Game game)
        {
            foreach (PedestrianSerializable ps in environment.Pedestrians)
            {
                AddPedestrian(ps, game);
            }
        }

        private Waypoint EnsureWaypoint(Vector2 waypointLocation, Game game)
        {
            // get existing waypoint at the pedestrians target
            Waypoint waypoint = Waypoints.SingleOrDefault(w => w.Location.Equals(waypointLocation));
            // create a new waypoint if no one exists at this location
            if (waypoint == null)
            {
                waypoint = new Waypoint(game, WaypointIdFactory.Instance.Id, waypointLocation);
                // add the new waypoint to the world
                Waypoints.Enqueue(waypoint);
            }

            return waypoint;
        }

        private void AddPedestrian(PedestrianSerializable ps, Game game)
        {
            Waypoint startWaypoint = EnsureWaypoint(ps.Location, game);
            Waypoint targetWaypoint = EnsureWaypoint(ps.Target, game);

            // create new pedestrian
            List<Stereotype> s = StereotypeFactory.Instance.GetRandomStereotype();
            PriorityQueue<int, WaitStrategy> str = StrategyFactory.Instance.GetWaitStrategiesByPrecedence();
            Pedestrian p = new Pedestrian(game, ps.Location.X, ps.Location.Y, startWaypoint, targetWaypoint, s, this, str);

            // add the new pedestrian to the world
            Pedestrians.Add(p);
        }

        private void LoadVertices()
        {
            int waypointsCount = Waypoints.Count;
            Vertex[] vertices = new Vertex[waypointsCount];
            int x = 0;
            foreach (Waypoint w in Waypoints)
            {
                Vertex vertex = new Vertex(w.Id);
                vertices[x++] = vertex;
                w.CorrespondingVertex = vertex;
            }

            Vertices = vertices;
        }

        private void LoadEdgesFromWaypoints()
        {
            Vector2[] waypoints = (from w in this.Waypoints select new Vector2(w.Location.X, w.Location.Y)).ToArray();
            Edge[] edges = null;
            int waypointsCount = waypoints.Length;
            // Maximum connections n(n-1)
            int maxConnections = waypointsCount * (waypointsCount - 1);

            edges = new Edge[maxConnections];

            int edgesCounter = 0;

            for (int i = 0; i < waypoints.Length; i++)
            {
                for (int j = i + 1; j < waypoints.Length; j++)
                {
                    int fromNode = i;
                    int toNode = j;
                    float cost = Vector2.Distance(waypoints[fromNode], waypoints[toNode]);

                    bool isValidConnection = true;

                    // Check if a connection from point a to point b is possible
                    Line tmpLine = new Line(waypoints[fromNode], waypoints[toNode]);

                    foreach (Obstacle o in this.Obstacles)
                    {
                        if (!isValidConnection) break;

                        foreach (Line l in o.Edges)
                        {
                            if (!isValidConnection) break;

                            isValidConnection = !CollisionDetector.Intersects(l, tmpLine);
                        }
                    }

                    if (isValidConnection)
                    {
                        Edge e = new Edge(Vertices[fromNode], Vertices[toNode], cost);

                        // Add the connection to the array and increment the index
                        edges[edgesCounter++] = e;
                    }
                }
            }

            // Resize the array to fit its content
            Edges = (from c in edges where c != null select c).ToArray();
        }

        #endregion
    }
}
